#include "USB2UARTSPIIICDLL.h"
#include <errno.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define VERSION "v0.1.0"

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

#define ASSERT(ok, fmt, ...)                                                                                           \
    if (!(ok)) {                                                                                                       \
        printf("[error] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__);                                         \
        exit(1);                                                                                                       \
    }

#define ASSERT_ERRNO(ok, fmt, ...)                                                                                     \
    if (!(ok)) {                                                                                                       \
        printf("[error] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__);                                         \
        if (errno) {                                                                                                   \
            printf("strerror: %s\n", strerror(errno));                                                                 \
        }                                                                                                              \
        exit(1);                                                                                                       \
    }

unsigned int g_usb_index = 0;

enum reg_access {
    REG_READ = 0,
    REG_WRITE = 1,
};

enum status {
    STATUS_CHECKSUM = 1 << 0,
    STATUS_LEN = 1 << 1,
    STATUS_READ = 1 << 2,
};

struct checksum {
    uint8_t parity_sum : 1; // parity checksum value
    uint8_t parity_sel : 1; // odd=1, even=0
    uint8_t n : 5;          // N reg
    uint8_t en : 1;         // check_en
};

#if 0
void show_dev_version(void) {
    unsigned char UID[12];        // UID
    unsigned int isHsUSB;         // USB 速度类型
    unsigned int firmwareVersion; //固件版本号

    if (IsFirstPowerUp(g_usb_index) == 1) {
        printf("no config\n");
    } else if (IsFirstPowerUp(g_usb_index) == 0) {
        printf("have config\n");
    }

    int ret = GetBoardInformation(UID, &isHsUSB, &firmwareVersion, g_usb_index);
    ASSERT(ret == 0, "Get board info failed (%d)", ret)

    printf("uid\n");
    // show UID
    for (int i = 0; i < ARRAY_SIZE(UID); i++) {
        if (i == 0) {
            printf("UID:");
        }

        if (i == ARRAY_SIZE(UID) - 1) {
            printf("\n");
            return;
        }

        printf(" %02x", UID[i]);
    }
    printf("end\n");
}
#endif

void open_master_dev(void) {
    int ret;

    CloseUsb(g_usb_index);

    ret = OpenUsb(g_usb_index);
    ASSERT_ERRNO(ret >= 0, "Can't open usb");

    ret = ConfigIICParam(IIC_Rate_400K, 0, g_usb_index);
    ASSERT(ret == 0, "config iic failed (%d)", ret);
}

// slave address（7bit）：高3bit = 0b011，低4bit = dienum[3:0]
uint8_t gen_slave_addr(uint8_t die) {
    if (die > 0x0F) {
        ASSERT(false, "die number over range!, die=0x%x, max=0x%d\n", die, 0x0F);
    }
    return (die & 0x0F);
}

// @checksum_flag: (bool) true: have checksum, false: not have checksum
uint8_t gen_reg_addr_23_16(bool checksum_flag, enum reg_access rw, uint32_t addr) {
    const uint32_t max_addr = 0x1FFFFF;
    uint8_t tmpaddr = 0;
    uint8_t addr_20_16 = addr >> 16; // bit21 not used!

    tmpaddr = (checksum_flag == true) ? tmpaddr | (0x1 << 8) : tmpaddr;
    tmpaddr = (rw == REG_READ) ? tmpaddr | (0x1 << 7) : tmpaddr;

    if (addr > max_addr) {
        ASSERT(false, "addr over range! addr=0x%x, max_addr=0x%x", addr, max_addr)
    }

    return tmpaddr | addr_20_16;
}

uint8_t gen_reg_addr_15_08(uint32_t addr) { return (uint8_t)((addr >> 8) & 0xFF); }

uint8_t gen_reg_addr_07_00(uint32_t addr) { return (uint8_t)(addr & 0xFF); }

void gen_buf_from_data(uint8_t *buf, uint32_t data) {
    buf[0] = (uint8_t)(data >> 24);
    buf[1] = (uint8_t)(data >> 16);
    buf[2] = (uint8_t)(data >> 8);
    buf[3] = (uint8_t)(data >> 0);
}

void gen_data_from_buf(uint8_t *buf, uint32_t *data, int ndata) {
    for (int i = 0, j = 0; i < ndata; i++) {
        j = i * 4;
        data[i] = buf[j + 0] << 24 | buf[j + 1] << 16 | buf[j + 2] << 8 | buf[j + 3] << 0;
    }
}

void single_write_register(uint8_t die, uint32_t addr, uint32_t data) {
    int ret;
    uint8_t buf[8];

    buf[0] = gen_slave_addr(die) << 1;
    buf[1] = gen_reg_addr_23_16(false, REG_WRITE, addr);
    buf[2] = gen_reg_addr_15_08(addr);
    buf[3] = gen_reg_addr_15_08(addr);
    gen_buf_from_data(buf + 4, data);

    ret = IICSendData(1, 1, buf, 8, g_usb_index);
    ASSERT(ret == 0, "send data failed (%d)", ret);
}

uint32_t single_read_register(uint8_t die, uint32_t addr) {
    int ret;
    uint8_t buf[4];
    uint32_t data;

    buf[0] = gen_slave_addr(die) << 1;
    buf[1] = gen_reg_addr_23_16(false, REG_READ, addr);
    buf[2] = gen_reg_addr_15_08(addr);
    buf[3] = gen_reg_addr_15_08(addr);

    // send: start, 7bit slave addr, rw 0
    ret = IICSendData(1, 0, buf, 4, g_usb_index);
    ASSERT(ret == 0, "send data failed (%d)", ret);

    // send: restart, 7bit slave addr, rw 1
    buf[0] = gen_slave_addr(die) << 1 | 0x1; // according to iic_ctrl usage, iic_rw bit = write
    ret = IICSendData(1, 0, buf, 4, g_usb_index);
    ASSERT(ret == 0, "send data failed (%d)", ret);

    // recv: 32bit data, stop
    memset(buf, 0, sizeof(buf));
    ret = IICRcvData(1, buf, 4, g_usb_index);
    ASSERT(ret == 0, "send data failed (%d)", ret);

    gen_data_from_buf(buf, &data, 1);
    return data;
}

int do_checksum(bool parity_sel, uint32_t *data, int n) {
    int sum = 0;
    return sum;
}

void multiple_write_register(uint8_t die, struct checksum checksum, uint32_t addr, uint32_t *data, int n) {
    int ret;
    uint8_t buf[4];

    buf[0] = gen_slave_addr(die) << 1;

    buf[1] = gen_reg_addr_23_16(checksum.en, REG_READ, addr);
    buf[2] = gen_reg_addr_15_08(addr);
    buf[3] = gen_reg_addr_07_00(addr);

    // send: start bit, slave address 7bit, iic_rw 0 | reg address 3B
    ret = IICSendData(1, 0, buf, 4, g_usb_index);
    //
}

void multiple_read_register(uint8_t die, struct checksum checksum, uint) {}

void show_data(uint32_t addr, uint32_t *data, int ndata) {
    const int ndata_per_line = 4;
    const int addr_step = ndata_per_line * sizeof(data[0]);

    for (int i = 0; i < ndata; i++) {
        if (i % ndata_per_line == 0) {
            if (i != 0) {
                printf("\n");
            }
            printf("%x:", addr + (i / ndata_per_line) * addr_step);
        }
        printf(" %x", data[i]);
    }
    printf("\n");
}

/* define long options */

char *app_name = NULL;

void print_version(void) { printf("version: %s  %s  %s\n", VERSION, __DATE__, __TIME__); }

void print_help(void) {
    printf("Usage: %s [options]\n", app_name);
    printf("options:\n");
    printf("  -d, --dir <die_num>                               die number\n");
    printf("  -r, --single-read  <addr>                         single read register\n");
    printf("  -w, --single-write <addr> <data>                  single write register\n");
    printf("  -R, --multiple-read  <addr> <N>                   multiple read register\n");
    printf("  -W, --multiple-write <addr> <data> ... <data N>   multiple write register\n");
    printf("  -v, --version                                     print version\n");
    printf("  -h, --help                                        print help\n");
}

uint32_t stru32(const char *str) {
    char *endptr;
    errno = 0;
    uint32_t val = strtoul(str, &endptr, 16);
    ASSERT_ERRNO(errno == 0, "strtoul failed: str=%s", str);
    ASSERT(*endptr == '\0', "invalid number string part: %s", endptr);
    ASSERT(val <= UINT32_MAX, "number overflow: %s\n", str);
    return val;
}

int parse_args(int argc, char *argv[]) {
    app_name = argv[0];
    int opt;
    int option_index = 0;

    uint8_t die = 0;
    uint32_t data[32] = {0};

    // 定义长选项
    struct option long_options[] = {
        {"die", required_argument, 0, 'd'},            // 必须参数
        {"single-read", required_argument, 0, 'r'},    // 必须参数
        {"single-write", required_argument, 0, 'w'},   // 必须参数
        {"multiple-read", required_argument, 0, 'R'},  // 必须参数
        {"multiple-write", required_argument, 0, 'W'}, // 必须参数
        {"version", no_argument, 0, 'v'},              // 无参数
        {"help", no_argument, 0, 'h'},                 // 无参数
        {0, 0, 0, 0},
    };

    // getopt_long 循环解析
    while ((opt = getopt_long(argc, argv, "d:r:w:vh", long_options, &option_index)) != -1) {
        switch (opt) {
            case 'd':
                die = (uint8_t)atoi(optarg);
                break;
            case 'r': {
                uint32_t addr = strtol(optarg, NULL, 16);
                printf("die: %d, addr: 0x%x\n", die, addr);
                data[0] = single_read_register(die, addr);
                show_data(addr, data, 1);
                return 0;
            }
            case 'w': {
                char *addr_str = optarg; // 第一个参数
                char *value_str = NULL;      // 第二个参数

                // 检查是否还有第二个参数
                if (optind < argc) {
                    value_str = argv[optind];
                    optind++; // 消耗这个参数
                } else {
                    fprintf(stderr, "option: -w need 2 args: [addr] [data]\n");
                    return -1;
                }

                uint32_t addr = stru32(addr_str);
                uint32_t value = stru32(value_str);
                single_write_register(die, addr, value);
                return 0;
            }
            case 'v': {
                print_version();
                return 0;
            }
            case 'h':
            case '?': {
                print_help();
                return 0;
            }
            default: {
                printf("NEVER! unknown opt: %c\n", opt);
                return 1;
            }
        }
    }

    print_help();
    return 1;
}

int main(int argc, char *argv[]) {
    // do parse_args
    return parse_args(argc, argv);
#if 0
  uint32_t data[32] = {
      0x12345600, 0x12345620, 0x12345640, 0x12345680, 0x12345601, 0x12345621,
      0x12345641, 0x12345681, 0x12345602, 0x12345622, 0x12345642, 0x12345682,
      0x12345603, 0x12345623, 0x12345643, 0x12345683, 0x12345604, 0x12345624,
      0x12345644, 0x12345684, 0x12345605, 0x12345625, 0x12345645, 0x12345685,
      0x12345606, 0x12345626, 0x12345646, 0x12345686, 0x12345607, 0x12345627,
      0x12345647, 0x12345687,
  };

  show_data(0xaa550000, data, 31);
#endif
}
